<?php
/**
 * @file
 * Implements Drupal Node hooks to create the chado_analysis node content type.
 *
 * @ingroup tripal_legacy_pub
 */

/**
 * Implements hook_node_info().
 *
 * Provide information to drupal about the node types that we're creating
 * in this module
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_info() {
  $nodes = [];
  $nodes['chado_pub'] = [
    'name' => t('Publication (Tripal v2 legacy)'),
    'base' => 'chado_pub',
    'description' => t('A publication from the Chado database'),
    'has_title' => TRUE,
    'locked' => TRUE,
    'chado_node_api' => [
      'base_table' => 'pub',
      'hook_prefix' => 'chado_pub',
      'record_type_title' => [
        'singular' => t('Publication'),
        'plural' => t('Publications'),
      ],
      'sync_filters' => [
        'type_id' => FALSE,
        'organism_id' => FALSE,
      ],
    ],
  ];
  return $nodes;
}

/**
 * Implements hook_form().
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_form($node, $form_state) {
  $form = [];

  // Check to make sure that the tripal_pub vocabulary is loaded. If not, then
  // warn the user that they should load it before continuing.
  $pub_cv = chado_select_record('cv', ['cv_id'], ['name' => 'tripal_pub']);
  if (count($pub_cv) == 0) {
    drupal_set_message(t('The Tripal Pub vocabulary is currently not loaded. ' .
      'This vocabulary is required to be loaded before adding ' .
      'publications.  <br>Please !import',
      ['!import' => l('load the Tripal Publication vocabulary', 'admin/tripal/loaders/chado_vocabs/obo_loader')]), 'warning');
  }

  // Default values can come in the following ways:
  //
  // 1) as elements of the $node object.  This occurs when editing an existing pub
  // 2) in the $form_state['values'] array which occurs on a failed validation or
  //    ajax callbacks from non submit form elements
  // 3) in the $form_state['input'] array which occurs on ajax callbacks from submit
  //    form elements and the form is being rebuilt
  //
  // set form field defaults
  $pub_id = NULL;
  $title = '';
  $pyear = '';
  $uniquename = '';
  $type_id = '';
  $is_obsolete = '';

  // some of the fields in the pub table should show up in the properties
  // form elements to make the form more seemless.  We will add them
  // to this array.
  $more_props = [];

  // if we are editing an existing node then the pub is already part of the node
  if (property_exists($node, 'pub')) {
    $pub = $node->pub;
    $pub = chado_expand_var($pub, 'field', 'pub.title');
    $pub = chado_expand_var($pub, 'field', 'pub.volumetitle');
    $pub = chado_expand_var($pub, 'field', 'pub.uniquename');
    $pub_id = $pub->pub_id;

    $title = $pub->title;
    $pyear = $pub->pyear;
    $uniquename = $pub->uniquename;
    $type_id = $pub->type_id->cvterm_id;
    $is_obsolete = $pub->is_obsolete;

    // if the obsolete value is set by the database then it is in the form of
    // 't' or 'f', we need to convert to 1 or 0
    $is_obsolete = $is_obsolete == 't' ? 1 : $is_obsolete;
    $is_obsolete = $is_obsolete == 'f' ? 0 : $is_obsolete;

    // set the organism_id in the form
    $form['pub_id'] = [
      '#type' => 'value',
      '#value' => $pub->pub_id,
    ];

    // get fields from the pub table and convert them to properties. We will add these to the $more_props
    // array which gets passed in to the tripal_core_properties_form() API call further down
    if ($pub->volumetitle) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Volume Title',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->volumetitle];
    }
    if ($pub->volume) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Volume',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->volume];
    }
    if ($pub->series_name) {
      switch ($pub->type_id->name) {
        case 'Journal Article':
          $cvterm = tripal_get_cvterm([
            'name' => 'Journal Name',
            'cv_id' => ['name' => 'tripal_pub'],
          ]);
          $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->series_name];
          break;
        case 'Conference Proceedings':
          $cvterm = tripal_get_cvterm([
            'name' => 'Conference Name',
            'cv_id' => ['name' => 'tripal_pub'],
          ]);
          $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->series_name];
          break;
        default:
          $cvterm = tripal_get_cvterm([
            'name' => 'Series Name',
            'cv_id' => ['tripal_pub'],
          ]);
          $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->series_name];
      }
    }
    if ($pub->issue) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Issue',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->issue];
    }
    if ($pub->pages) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Pages',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->pages];
    }
    if ($pub->miniref) {
      // not sure what to do with this one
    }
    if ($pub->publisher) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Publisher',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->publisher];
    }
    if ($pub->pubplace) {
      $cvterm = tripal_get_cvterm([
        'name' => 'Published Location',
        'cv_id' => ['name' => 'tripal_pub'],
      ]);
      $more_props[] = ['cvterm' => $cvterm, 'value' => $pub->pages];
    }
  }
  // if we are re constructing the form from a failed validation or ajax callback
  // then use the $form_state['values'] values
  if (array_key_exists('values', $form_state) and isset($form_state['values']['pubtitle'])) {
    $title = $form_state['values']['pubtitle'];
    $pyear = $form_state['values']['pyear'];
    $uniquename = $form_state['values']['uniquename'];
    $type_id = $form_state['values']['type_id'];
    $is_obsolete = $form_state['values']['is_obsolete'];
  }
  // if we are re building the form from after submission (from ajax call) then
  // the values are in the $form_state['input'] array
  if (array_key_exists('input', $form_state) and !empty($form_state['input'])) {
    $title = $form_state['input']['pubtitle'];
    $uniquename = $form_state['input']['uniquename'];
    $type_id = $form_state['input']['type_id'];
    $is_obsolete = array_key_exists('is_obsolete', $form_state['input']) ? $form_state['input']['is_obsolete'] : '';
  }

  $form['pubtitle'] = [
    '#type' => 'textarea',
    '#title' => t('Publication Title'),
    '#default_value' => $title,
    '#required' => TRUE,
  ];

  $type_cv = tripal_get_default_cv('pub', 'type_id');
  if ($type_cv and $type_cv->name == 'tripal_pub') {

    // get the list of publication types.  In the Tripal publication
    // ontologies these are all grouped under the term 'Publication Type'
    // we want the default to be 'Journal Article'
    $sql = "
      SELECT
        CVTS.cvterm_id, CVTS.name
      FROM {cvtermpath} CVTP
        INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
        INNER JOIN {cvterm} CVTO ON CVTP.object_id  = CVTO.cvterm_id
        INNER JOIN {cv}          ON CVTO.cv_id      = CV.cv_id
      WHERE
        CV.name = 'tripal_pub' AND CVTO.name = 'Publication Type' AND
        NOT CVTS.is_obsolete = 1
      ORDER BY CVTS.name ASC
    ";
    $results = chado_query($sql);
    $pub_types = [];
    while ($pub_type = $results->fetchObject()) {
      $pub_types[$pub_type->cvterm_id] = $pub_type->name;
      // if we don't have a default type then set the default to be 'Journal Article'
      if (strcmp($pub_type->name, "Journal Article") == 0 and !$type_id) {
        $type_id = $pub_type->cvterm_id;
      }
    }
  }
  else {
    $pub_types = tripal_get_cvterm_default_select_options('pub', 'type_id', 'publication types');
    $pub_types[0] = 'Select a Type';
  }

  $form['type_id'] = [
    '#type' => 'select',
    '#title' => t('Publication Type'),
    '#options' => $pub_types,
    '#required' => TRUE,
    '#default_value' => $type_id,
  ];
  $form['pyear'] = [
    '#type' => 'textfield',
    '#title' => t('Publication Year'),
    '#default_value' => $pyear,
    '#required' => TRUE,
    '#size' => 5,
    '#description' => t('Enter the year of publication. Also, if available, please add a <b>Publication Date</b> property to specify the full date of publication.'),
  ];
  $form['uniquename'] = [
    '#type' => 'textarea',
    '#title' => t('Citation'),
    '#default_value' => $uniquename,
    '#description' => t('All publications must have a unique citation.
      <b>Please enter the full citation for this publication or leave blank and one will be generated
      automatically if possible</b>.  For PubMed style citations list
      the last name of the author followed by initials. Each author should be separated by a comma. Next comes
      the title, followed by the series title (e.g. journal name), publication date (4 digit year, 3 character Month, day), volume, issue and page numbers. You may also use HTML to provide a link in the citation.
      Below is an example: <pre>Medeiros PM, Ladio AH, Santos AM, Albuquerque UP. <a href="http://www.ncbi.nlm.nih.gov/pubmed/23462414" target="_blank">Does the selection of medicinal plants by Brazilian local populations
        suffer taxonomic influence?</a> J Ethnopharmacol. 2013 Apr 19; 146(3):842-52.</pre>'),
  ];
  $form['is_obsolete'] = [
    '#type' => 'checkbox',
    '#title' => t('Is Obsolete? (Check for Yes)'),
    '#default_value' => $is_obsolete,
  ];

  // Properties Form
  // ----------------------------------
  $select_options = [];
  $prop_cv = tripal_get_default_cv('pubprop', 'type_id');
  $cv_id = $prop_cv ? $prop_cv->cv_id : NULL;
  // if the poperty cv is 'tripal_pub' then we need to pass in our own select_options
  // for only a subset of the vocabulary
  if ($prop_cv and $prop_cv->name == 'tripal_pub') {
    $select_options[] = 'Select a Property';
    $sql = "
      SELECT
        DISTINCT CVTS.cvterm_id, CVTS.name, CVTS.definition
      FROM {cvtermpath} CVTP
        INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
        INNER JOIN {cvterm} CVTO ON CVTP.object_id  = CVTO.cvterm_id
        INNER JOIN {cv}          ON CVTO.cv_id      = CV.cv_id
      WHERE CV.name = 'tripal_pub' and
        (CVTO.name = 'Publication Details' OR CVTS.name = 'Publication Type') AND
        NOT CVTS.is_obsolete = 1
      ORDER BY CVTS.name ASC
    ";
    $prop_types = chado_query($sql);
    while ($prop = $prop_types->fetchObject()) {
      // add all properties except the Citation. That property is set via the uniquename field
      if ($prop->name == 'Citation') {
        continue;
      }
      // Publication Dbxref's are handled by the dbxref form addition below
      if ($prop->name == 'Publication Dbxref') {
        continue;
      }
      $select_options[$prop->cvterm_id] = $prop->name;
    }
  }

  $details = [
    'property_table' => 'pubprop',
    'chado_id' => $pub_id,
    'cv_id' => $cv_id,
    'select_options' => $select_options,
    'default_properties' => $more_props,
  ];
  chado_add_node_form_properties($form, $form_state, $details);

  // RELATIONSHIPS FORM
  //---------------------------------------------
  $relationship_cv = tripal_get_default_cv('pub_relationship', 'type_id');
  $cv_id = $relationship_cv ? $relationship_cv->cv_id : NULL;
  $details = [
    'relationship_table' => 'pub_relationship',
    // the name of the _relationship table
    'base_table' => 'pub',
    // the name of your chado base table
    'base_foreign_key' => 'pub_id',
    // the name of the key in your base chado table
    'base_key_value' => $pub_id,
    // the value of pub_id for this record
    'nodetype' => 'pub',
    // the human-readable name of your node type
    'cv_id' => $cv_id,
    // the cv.cv_id of the cv containing the relationships
    'base_name_field' => 'uniquename',
    // the base table field you want to be used as the name
  ];
  // Adds the form elements to your current form
  chado_add_node_form_relationships($form, $form_state, $details);


  // ADDITIONAL DBXREFS FORM
  //---------------------------------------------
  $details = [
    'linking_table' => 'pub_dbxref',
    // the name of the _dbxref table
    'base_foreign_key' => 'pub_id',
    // the name of the key in your base chado table
    'base_key_value' => $pub_id
    // the value of pub_id for this record
  ];
  // Adds the form elements to your current form
  chado_add_node_form_dbxrefs($form, $form_state, $details);

  return $form;

}

/**
 * Implements hook_validate().
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_validate($node, $form, &$form_state) {

  // We only want to validate when the node is saved.
  // Since this validate can be called on AJAX and Deletion of the node
  // we need to make this check to ensure queries are not executed
  // without the proper values.
  if (property_exists($node, "op") and $node->op != 'Save') {
    return;
  }

  // we are syncing if we do not have a node ID but we do have a pub_id. We don't
  // need to validate during syncing so just skip it.
  if (!property_exists($node, 'nid') and property_exists($node, 'pub_id') and $node->pub_id != 0) {
    return;
  }

  // get the submitted values
  $title = property_exists($node, 'pubtitle') ? trim($node->pubtitle) : '';
  $pyear = property_exists($node, 'pyear') ? trim($node->pyear) : '';
  $uniquename = property_exists($node, 'uniquename') ? trim($node->uniquename) : '';
  $is_obsolete = property_exists($node, 'is_obsolete') ? trim($node->is_obsolete) : 0;
  $type_id = property_exists($node, 'type_id') ? trim($node->type_id) : '';

  $pub = [];

  // make sure the year is four digits
  if (!preg_match('/^\d{4}$/', $pyear)) {
    form_set_error('pyear', t('The publication year should be a 4 digit year.'));
    return;
  }

  // get the type of publication
  $values = ['cvterm_id' => $type_id];
  $cvterm = chado_select_record('cvterm', ['name'], $values);
  if (count($cvterm) == 0) {
    $message = t('Invalid publication type.');
    form_set_error('type_id', $message);
    return;
  }

  // Get the media name looking at the properties
  $series_name = '';
  $properties = chado_retrieve_node_form_properties($node);
  foreach ($properties as $key => $prop_values) {
    $values = ['cvterm_id' => $key];
    $prop_type = chado_select_record('cvterm', ['name'], $values);
    if ($prop_type[0]->name == 'Conference Name' or
      $prop_type[0]->name == 'Journal Name' or
      $prop_type[0]->name == 'Series Name') {
      $v = array_values($prop_values);
      $series_name = $v[0];
    }
    if ($prop_type[0]->name == 'Citation') {
      $v = array_values($prop_values);
      $uniquename = $v[0];
    }
    if (count($prop_values) == 1) {
      $v = array_values($prop_values);
      $pub[$prop_type[0]->name] = $v[0];
    }
    else {
      $pub[$prop_type[0]->name] = $prop_values;
    }
  }
  // if the citation is missing then try to generate one
  if (!$uniquename) {
    $pub['Title'] = $title;
    $pub['Publication Type'][0] = $cvterm[0]->name;
    $pub['Year'] = $pyear;
    $uniquename = tripal_pub_create_citation($pub);
    if (!$uniquename) {
      form_set_error('uniquename', "Cannot automatically generate a citation for this publication type. Please add one manually.");
    }
  }

  $skip_duplicate_check = 0;

  // if this publication is a Patent then skip the validation below.  Patents can have the title
  // name and year but be different
  if (strcmp($cvterm[0]->name, 'Patent') == 0) {
    $skip_duplicate_check = 1;
  }

  // Validating for an update
  if (!is_null($node->nid)) {

    $pub_id = $node->pub_id;

    // check to see if a duplicate publication already exists
    if (!$skip_duplicate_check) {
      chado_pub_validate_check_duplicate($title, $pyear, $series_name, $cvterm[0], $pub_id);
    }
    chado_pub_validate_check_uniquename($uniquename, $pub_id);
  }
  // Validating for an insert
  else {
    chado_pub_validate_check_duplicate($title, $pyear, $series_name, $cvterm[0]);
    chado_pub_validate_check_uniquename($uniquename);
  }
}

/**
 * Validate the publication uniquename. To be called from hook_validate().
 *
 * @param $uniquename
 *  The uniquename of the publication
 * @param $pub_id
 *  If an update, provide the pub_id so we don't check for a matching
 *  uniquename of the pub we are editing
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_validate_check_uniquename($uniquename, $pub_id = NULL) {

  // check to see if a pub exists with this uniquename
  $pub = tripal_get_publication(['uniquename' => $uniquename]);
  if ($pub) {
    // if a $pub_id is provided to the function then this is an update
    // if the pub_id's don't match then a different pub with the same
    // uniquename already exists.
    if ($pub->pub_id != $pub_id) {
      $message = t('A publication with this unique citation already exists.');
      form_set_error('uniquename', $message);
    }
  }
}

/**
 * Check for duplicate publications. To be called from hook_validate().  Sets
 * the form error if a duplicate
 *
 * @param $title
 *   The title of the publication
 * @param $pyear
 *   The year the publication was published
 * @param $series_name
 *   The series name of the publication
 * @param $cvterm
 *   The type of publication
 * @param $pub_id
 *   the unique id of the publication
 *
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_validate_check_duplicate($title, $pyear, $series_name, $cvterm, $pub_id = NULL) {

  $pub_details = [
    'Title' => $title,
    'Year' => $pyear,
    'Series Name' => $series_name,
    'Publication Type' => $cvterm->name,
  ];
  // TODO: need to include the Publication Dbxref in the $pub_details as well
  $pub_ids = tripal_publication_exists($pub_details);

  // if we found only one publication and it is our publication then return, we're good.
  if (count($pub_ids) == 1 and in_array($pub_id, $pub_ids)) {
    return;
  }
  if (count($pub_ids) == 0) {
    // there is no match so return
    return;
  }

  // return an appropriate message based on the unique constraint settings
  $import_dups_check = variable_get('tripal_pub_import_duplicate_check', 'title_year_media');
  switch ($import_dups_check) {
    case 'title_year':
      $message = t('A publication with this title and publication year already exists.');
      form_set_error('pubtitle', $message);
      break;
    case 'title_year_type':
      $message = t('A publication with this title, type and publication year already exists.');
      form_set_error('pubtitle', $message);
      break;
    case 'title_year_media':
      $message = t('A publication with this title, media name (e.g. Journal Name) and publication year already exists.');
      form_set_error('pubtitle', $message);
      break;
  }
}

/**
 * Implement hook_node_access().
 *
 * This hook allows node modules to limit access to the node types they define.
 *
 * @param $node
 *  The node on which the operation is to be performed, or, if it does not yet
 *   exist, the type of node to be created
 *
 * @param $op
 *  The operation to be performed
 *
 * @param $account
 *  A user object representing the user for whom the operation is to be
 *   performed
 *
 * @return
 *  If the permission for the specified operation is not set then return FALSE.
 *   If the permission is set then return NULL as this allows other modules to
 *   disable access.  The only exception is when the $op == 'create'.  We will
 *   always return TRUE if the permission is set.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_access($node, $op, $account) {
  $node_type = $node;
  if (is_object($node)) {
    $node_type = $node->type;
  }

  if ($node_type == 'chado_pub') {
    if ($op == 'create') {
      if (!user_access('create chado_pub content', $account)) {
        return NODE_ACCESS_DENY;
      }
      return NODE_ACCESS_ALLOW;
    }
    if ($op == 'update') {
      if (!user_access('edit chado_pub content', $account)) {
        return NODE_ACCESS_DENY;
      }
    }
    if ($op == 'delete') {
      if (!user_access('delete chado_pub content', $account)) {
        return NODE_ACCESS_DENY;
      }
    }
    if ($op == 'view') {
      if (!user_access('access chado_pub content', $account)) {
        return NODE_ACCESS_DENY;
      }
    }
    return NODE_ACCESS_IGNORE;
  }
}

/**
 * Implements hook_insert().
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_insert($node) {

  $pub_id = '';

  // if there is an pub_id in the $node object then this must be a sync so
  // we can skip adding the pub as it is already there, although
  // we do need to proceed with insertion into the chado/drupal linking table.
  if (!property_exists($node, 'pub_id')) {

    $node->pubtitle = trim($node->pubtitle);
    $node->pyear = trim($node->pyear);
    $node->uniquename = trim($node->uniquename);
    $is_obsolete = $node->is_obsolete;
    $type_id = $node->type_id;

    // we need an array suitable for the tripal_pub_create_citation() function
    // to automatically generate a citation if a uniquename doesn't already exist
    $pub_arr = [];

    // Stores all of the properties we need to add.
    $properties = [];
    // Stores any cross references for this publication.
    $cross_refs = [];

    // get the properties from the form
    $properties = chado_retrieve_node_form_properties($node);

    // get the list of properties for easy lookup (without doing lots of database queries
    $properties_list = [];
    $sql = "
      SELECT CVTS.cvterm_id, CVTS.name, CVTS.definition
      FROM {cvtermpath} CVTP
        INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
        INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
        INNER JOIN {cv} ON CVTO.cv_id = CV.cv_id
      WHERE CV.name = 'tripal_pub' and CVTO.name = 'Publication Details' and
        NOT CVTS.is_obsolete = 1
      ORDER BY CVTS.name ASC
    ";
    $prop_types = chado_query($sql);
    while ($prop = $prop_types->fetchObject()) {
      $properties_list[$prop->cvterm_id] = $prop->name;
      // The 'Citation' term is special because it serves
      // both as a property and as the uniquename for the
      // pub and we want it stored in both the pub table and the pubprop table
      if ($prop->name == 'Citation') {
        $citation_id = $prop->cvterm_id;
        if (!empty($node->uniquename)) {
          $properties[$citation_id][0] = $node->uniquename;
        }
      }
    }

    // iterate through all of the properties and remove those that really are
    // part of the pub table fields
    $volume = '';
    $volumetitle = '';
    $issue = '';
    $pages = '';
    $publisher = '';
    $series_name = '';
    $pubplace = '';
    $miniref = '';
    $cross_refs = [];
    foreach ($properties as $type_id => $element) {
      foreach ($element as $index => $value) {
        $name = $properties_list[$type_id];

        // populate our $pub_array for building a citation
        $pub_arr[$name] = $value;

        // remove properties that are stored in the pub table
        if ($name == "Volume") {
          $volume = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Volume Title") {
          $volumetitle = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Issue") {
          $issue = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Pages") {
          $pages = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Publisher") {
          $publisher = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Series Name" or $name == "Journal Name" or $name == "Conference Name") {
          $series_name = $value;
          unset($properties[$type_id]);
        }
        elseif ($name == "Journal Country" or $name == "Published Location") {
          $pubplace = $value;
          // allow this property to go into the pubprop table so we don't loose info
          // so don't unset it. But it will also go into the pub.pubplace field
        }
        elseif ($name == "Publication Dbxref") {
          // we will add the cross-references to the pub_dbxref table
          // but we also want to keep the property in the pubprop table so don't unset it
          $cross_refs[] = $value;
        }
      }
    }
    // generate a citation for this pub if one doesn't already exist
    if (!$node->uniquename and !array_key_exists($citation_id, $properties)) {
      $pub_type = tripal_get_cvterm(['cvterm_id' => $node->type_id]);
      $pub_arr['Title'] = $node->pubtitle;
      $pub_arr['Publication Type'][0] = $pub_type->name;
      $pub_arr['Year'] = $node->pyear;
      $node->uniquename = tripal_pub_create_citation($pub_arr);
      $properties[$citation_id][0] = $node->uniquename;
    }

    // insert the pub record
    $values = [
      'title' => $node->pubtitle,
      'series_name' => substr($series_name, 0, 255),
      'type_id' => $node->type_id,
      'pyear' => $node->pyear,
      'is_obsolete' => $node->is_obsolete ? 'true' : 'false',
      'uniquename' => $node->uniquename,
      'volumetitle' => $volumetitle,
      'volume' => $volume,
      'issue' => $issue,
      'pages' => $pages,
      'miniref' => substr($miniref, 0, 255),
      'publisher' => substr($publisher, 0, 255),
      'pubplace' => substr($pubplace, 0, 255),
    ];
    $pub = chado_insert_record('pub', $values);
    if (!$pub) {
      drupal_set_message("Error inserting publication", "error");
      watchdog('tripal_pub', "Error inserting publication", [], WATCHDOG_ERROR);
      return;
    }
    $pub_id = $pub['pub_id'];

    // now add in the properties
    // Only adds in those not used in the pub record
    $details = [
      'property_table' => 'pubprop',
      'base_table' => 'pub',
      'foreignkey_name' => 'pub_id',
      'foreignkey_value' => $pub_id,
    ];
    chado_update_node_form_properties($node, $details, $properties);

    // * Relationships Form *
    $details = [
      'relationship_table' => 'pub_relationship',
      // name of the _relationship table
      'foreignkey_value' => $pub_id
      // value of the pub_id key
    ];
    chado_update_node_form_relationships($node, $details);

    // add in any database cross-references
    foreach ($cross_refs as $index => $ref) {
      $dbxref = [];
      if (preg_match('/^(.*?):(.*?)$/', trim($ref), $matches)) {
        $dbxref['db_name'] = $matches[1];
        $dbxref['accession'] = $matches[2];
      }
      $success = chado_associate_dbxref('pub', $pub['pub_id'], $dbxref);
      if (!$success) {
        drupal_set_message("Error cannot add publication cross reference: $ref", "error");
        watchdog('tripal_pub', "Error cannot add publication cross reference: %ref",
          ['%ref' => $ref], WATCHDOG_ERROR);
      }
    }

    // * Additional DBxrefs Form *
    $details = [
      'linking_table' => 'pub_dbxref',
      // the name of your _dbxref table
      'foreignkey_name' => 'pub_id',
      // the name of the key in your base table
      'foreignkey_value' => $pub_id
      // the value of the pub_id key
    ];
    chado_update_node_form_dbxrefs($node, $details);
  }
  else {
    $pub_id = $node->pub_id;
  }

  // Make sure the entry for this pub doesn't already exist in the
  // chado_pub table if it doesn't exist then we want to add it.
  $check_org_id = chado_get_id_from_nid('pub', $node->nid);
  if (!$check_org_id) {
    $record = new stdClass();
    $record->nid = $node->nid;
    $record->vid = $node->vid;
    $record->pub_id = $pub_id;
    drupal_write_record('chado_pub', $record);
  }

}

/**
 * Implements hook_update().
 *
 * The purpose of the function is to allow the module to take action when an
 * edited node is being updated. It updates any name changes to the database
 * tables that werec reated upon registering a Publication. As well, the
 * database will be changed, so the user changed information will be saved to
 * the database.
 *
 * @param $node
 *   The node being updated
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_update($node) {
  $node->pubtitle = trim($node->pubtitle);
  $node->pyear = trim($node->pyear);
  $node->uniquename = trim($node->uniquename);
  $is_obsolete = $node->is_obsolete;
  $type_id = $node->type_id;

  // we need an array suitable for the tripal_pub_create_citation() function
  // to automatically generate a citation if a uniquename doesn't already exist
  $pub_arr = [];

  // get the publication ID for this publication
  $pub_id = chado_get_id_from_nid('pub', $node->nid);

  $properties = []; // stores all of the properties we need to add
  $cross_refs = []; // stores any cross references for this publication

  // get the properties from the form
  $properties = chado_retrieve_node_form_properties($node);

  // get the list of properties for easy lookup (without doing lots of database queries
  $properties_list = [];
  $sql = "
    SELECT DISTINCT CVTS.cvterm_id, CVTS.name, CVTS.definition
    FROM {cvtermpath} CVTP
      INNER JOIN {cvterm} CVTS ON CVTP.subject_id = CVTS.cvterm_id
      INNER JOIN {cvterm} CVTO ON CVTP.object_id = CVTO.cvterm_id
      INNER JOIN {cv} ON CVTO.cv_id = CV.cv_id
    WHERE CV.name = 'tripal_pub' and
      (CVTO.name = 'Publication Details' or CVTS.name = 'Publication Type') and
      NOT CVTS.is_obsolete = 1
    ORDER BY CVTS.name ASC
  ";
  $prop_types = chado_query($sql);
  while ($prop = $prop_types->fetchObject()) {
    $properties_list[$prop->cvterm_id] = $prop->name;
    // The 'Citation' term is special because it serves
    // both as a property and as the uniquename for the
    // pub and we want it stored in both the pub table and the pubprop table
    if ($prop->name == 'Citation') {
      $citation_id = $prop->cvterm_id;
      if (!empty($node->uniquename)) {
        $properties[$citation_id][0] = $node->uniquename;
      }
    }
  }

  // iterate through all of the properties and remove those that really are
  // part of the pub table fields
  $volume = '';
  $volumetitle = '';
  $issue = '';
  $pages = '';
  $publisher = '';
  $series_name = '';
  $pubplace = '';
  $miniref = '';
  $cross_refs = [];
  foreach ($properties as $type_id => $element) {
    foreach ($element as $index => $value) {
      $name = $properties_list[$type_id];
      // populate our $pub_array for building a citation
      $pub_arr[$name] = $value;

      // remove properties that are stored in the pub table
      if ($name == "Volume") {
        $volume = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Volume Title") {
        $volumetitle = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Issue") {
        $issue = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Pages") {
        $pages = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Publisher") {
        $publisher = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Journal Name" or $name == "Conference Name") {
        $series_name = $value;
        unset($properties[$type_id]);
      }
      elseif ($name == "Journal Country" or $name == "Published Location") {
        $pubplace = $value;
        // allow this property to go into the pubprop table so we don't loose info
        // so don't unset it. But it will also go into the pub.pubplace field
      }
      elseif ($name == "Publication Dbxref") {
        // we will add the cross-references to the pub_dbxref table
        // but we also want to keep the property in the pubprop table so don't unset it
        $cross_refs[] = $value;
      }
    }
  }
  // generate a citation for this pub if one doesn't already exist
  if (!$node->uniquename) {
    $pub_type = tripal_get_cvterm(['cvterm_id' => $node->type_id]);
    $pub_arr['Title'] = $node->pubtitle;
    $pub_arr['Publication Type'][0] = $pub_type->name;
    $pub_arr['Year'] = $node->pyear;
    $node->uniquename = tripal_pub_create_citation($pub_arr);
    $properties[$citation_id][0] = $node->uniquename;
  }

  // update the pub record
  $match = [
    'pub_id' => $pub_id,
  ];
  $values = [
    'title' => $node->pubtitle,
    'type_id' => $node->type_id,
    'pyear' => $node->pyear,
    'is_obsolete' => $node->is_obsolete ? 'true' : 'false',
    'uniquename' => $node->uniquename,
    'series_name' => substr($series_name, 0, 255),
    'volumetitle' => $volumetitle,
    'volume' => $volume,
    'issue' => $issue,
    'pages' => $pages,
    'miniref' => substr($miniref, 0, 255),
    'publisher' => substr($publisher, 0, 255),
    'pubplace' => substr($pubplace, 0, 255),
  ];
  $status = chado_update_record('pub', $match, $values);
  if (!$status) {
    drupal_set_message("Error updating publication", "error");
    watchdog('tripal_pub', "Error updating publication", [], WATCHDOG_ERROR);
    return;
  }

  // now add in the properties by first removing any the publication
  // already has and adding the ones we have
  $details = [
    'property_table' => 'pubprop',
    'base_table' => 'pub',
    'foreignkey_name' => 'pub_id',
    'foreignkey_value' => $pub_id,
  ];
  chado_update_node_form_properties($node, $details, $properties);

  // * Relationships Form *
  $details = [
    'relationship_table' => 'pub_relationship',
    // name of the _relationship table
    'foreignkey_value' => $pub_id
    // value of the pub_id key
  ];
  chado_update_node_form_relationships($node, $details);

  // add in any database cross-references after first removing
  chado_delete_record('pub_dbxref', ['pub_id' => $pub_id]);
  foreach ($cross_refs as $index => $ref) {
    $dbxref = [];
    if (preg_match('/^(.*?):(.*?)$/', trim($ref), $matches)) {
      $dbxref['db_name'] = $matches[1];
      $dbxref['accession'] = $matches[2];
    }
    $success = chado_associate_dbxref('pub', $pub_id, $dbxref);
    if (!$success) {
      drupal_set_message("Error cannot add publication cross reference: $ref", "error");
      watchdog('tripal_pub', "Error cannot add publication cross reference: %ref",
        ['%ref' => $ref], WATCHDOG_ERROR);
    }
  }

  // * Additional DBxrefs Form *
  $details = [
    'linking_table' => 'pub_dbxref',   // the name of your _dbxref table
    'foreignkey_name' => 'pub_id',     // the name of the key in your base table
    'foreignkey_value' => $pub_id      // the value of the pub_id key
  ];
  chado_update_node_form_dbxrefs($node, $details);
}

/**
 * Implements hook_load().
 *
 * @param $node
 *   The node that is to be loaded
 *
 * @return $node
 *   The node with the information to be loaded into the database
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_load($nodes) {

  foreach ($nodes as $nid => $node) {
    // find the pub and add in the details
    $pub_id = chado_get_id_from_nid('pub', $nid);

    // if the nid does not have a matching record then skip this node.
    // this can happen with orphaned nodes.
    if (!$pub_id) {
      continue;
    }

    // get the pub
    $values = ['pub_id' => $pub_id];
    $pub = chado_generate_var('pub', $values);

    // expand the 'text' fields as those aren't included by default
    // and they really shouldn't be so large to cause problems
    $pub = chado_expand_var($pub, 'field', 'pub.title');
    $pub = chado_expand_var($pub, 'field', 'pub.volumetitle');
    $pub = chado_expand_var($pub, 'field', 'pub.uniquename');

    // set the URL path
    $nodes[$nid]->path = "pub/$pub_id";
    $nodes[$nid]->pub = $pub;

    // Now get the title
    $node->title = chado_get_node_title($node);
  }
}

/**
 * Implements hook_delete().
 *
 * This function takes a node and if the delete button has been chosen by the
 * user, the publication and it's details will be removed.Following,given the
 * node-ID, the instance will be deleted from the 'chado_pub' table.
 *
 * @parm $node
 *    Then node to be deleted
 *
 * @ingroup tripal_legacy_pub
 */
function chado_pub_delete(&$node) {

  $pub_id = chado_get_id_from_nid('pub', $node->nid);

  // if we don't have a pub id for this node then this isn't a node of
  // type chado_pub or the entry in the chado_pub table was lost.
  if (!$pub_id) {
    return;
  }

  // Remove data from {chado_pub}, {node} and {node_revision} tables of
  // drupal database
  $sql_del = "DELETE FROM {chado_pub} WHERE nid = :nid AND vid = :vid";
  db_query($sql_del, [':nid' => $node->nid, ':vid' => $node->vid]);
  $sql_del = "DELETE FROM {node_revision} WHERE nid = :nid AND vid = :vid";
  db_query($sql_del, [':nid' => $node->nid, ':vid' => $node->vid]);
  $sql_del = "DELETE FROM {node} WHERE nid = :nid AND vid = :vid";
  db_query($sql_del, [':nid' => $node->nid, ':vid' => $node->vid]);

  // Remove data from pub and pubprop tables of chado database as well
  chado_query("DELETE FROM {pubprop} WHERE pub_id = :pub_id", [':pub_id' => $pub_id]);
  chado_query("DELETE FROM {pub} WHERE pub_id = :pub_id", [':pub_id' => $pub_id]);
}

/**
 * Implements hook_node_view(). Acts on all content types.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_view($node, $view_mode, $langcode) {
  switch ($node->type) {
    case 'chado_pub':
      // Show feature browser and counts
      if ($view_mode == 'full') {
        $node->content['tripal_pub_authors'] = [
          '#theme' => 'tripal_pub_authors',
          '#node' => $node,
          '#tripal_toc_id' => 'authors',
          '#tripal_toc_title' => 'Author Details',
        ];
        $node->content['tripal_pub_base'] = [
          '#theme' => 'tripal_pub_base',
          '#node' => $node,
          '#tripal_toc_id' => 'base',
          '#tripal_toc_title' => 'Overview',
          '#weight' => -100,
        ];
        $node->content['tripal_pub_featuremaps'] = [
          '#theme' => 'tripal_pub_featuremaps',
          '#node' => $node,
          '#tripal_toc_id' => 'featuremaps',
          '#tripal_toc_title' => 'Maps',
        ];
        $node->content['tripal_pub_features'] = [
          '#theme' => 'tripal_pub_features',
          '#node' => $node,
          '#tripal_toc_id' => 'features',
          '#tripal_toc_title' => 'Features',
        ];
        $node->content['tripal_pub_libraries'] = [
          '#theme' => 'tripal_pub_libraries',
          '#node' => $node,
          '#tripal_toc_id' => 'libraries',
          '#tripal_toc_title' => 'Libraries',
        ];
        $node->content['tripal_pub_projects'] = [
          '#theme' => 'tripal_pub_projects',
          '#node' => $node,
          '#tripal_toc_id' => 'projects',
          '#tripal_toc_title' => 'Projects',
        ];
        $node->content['tripal_pub_properties'] = [
          '#theme' => 'tripal_pub_properties',
          '#node' => $node,
          '#tripal_toc_id' => 'properties',
          '#tripal_toc_title' => 'Properties',
        ];
        $node->content['tripal_pub_references'] = [
          '#theme' => 'tripal_pub_references',
          '#node' => $node,
          '#tripal_toc_id' => 'references',
          '#tripal_toc_title' => 'Cross References',
        ];
        $node->content['tripal_pub_relationships'] = [
          '#theme' => 'tripal_pub_relationships',
          '#node' => $node,
          '#tripal_toc_id' => 'relationships',
          '#tripal_toc_title' => 'Relationships',
        ];
        $node->content['tripal_pub_stocks'] = [
          '#theme' => 'tripal_pub_stocks',
          '#node' => $node,
          '#tripal_toc_id' => 'stocks',
          '#tripal_toc_title' => 'Stocks',
        ];
      }
      if ($view_mode == 'teaser') {
        $node->content['tripal_pub_teaser'] = [
          '#theme' => 'tripal_pub_teaser',
          '#node' => $node,
        ];
      }
      break;
  }
}

/**
 * Implements hook_node_insert(). Acts on all content types.
 *
 * We want the publications to always have a URL of http://[base url]/pub/[pub
 * id] where [pub id] is the Chado publication ID.  This will allow for easy
 * linking into the publication without needing to know the node.  Of course if
 * you know the node that will still work too (e.g. http://[base
 * url]/node/[node id] so the nodeapi function ensures that the URL path is set
 * after insert or update of the node and when the node is loaded if it hasn't
 * yet been set.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_insert($node) {

  if ($node->type == 'chado_pub') {

    // get the pub
    $pub_id = chado_get_id_from_nid('pub', $node->nid);
    $values = ['pub_id' => $pub_id];
    $pub = chado_generate_var('pub', $values);

    // expand the 'text' fields as those aren't included by default
    // and they really shouldn't be so large to cause problems
    $pub = chado_expand_var($pub, 'field', 'pub.title');
    $pub = chado_expand_var($pub, 'field', 'pub.volumetitle');
    $pub = chado_expand_var($pub, 'field', 'pub.uniquename');
    $node->pub = $pub;

    // Now get the title
    $node->title = chado_get_node_title($node);

    tripal_pub_set_pub_url($node, $pub_id);
  }
}

/**
 * Implements hook_node_load(). Acts on all content types.
 *
 * We want the publications to always have a URL of http://[base url]/pub/[pub
 * id] where [pub id] is the Chado publication ID.  This will allow for easy
 * linking into the publication without needing to know the node.  Of course if
 * you know the node that will still work too (e.g. http://[base
 * url]/node/[node id] so the nodeapi function ensures that the URL path is set
 * after insert or update of the node and when the node is loaded if it hasn't
 * yet been set.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_load($nodes, $types) {
  if (count(array_intersect(['chado_pub'], $types))) {
    foreach ($nodes as $nid => $node) {
      if ($node->type == 'chado_pub' and !drupal_lookup_path('alias', 'node/' . $nid)) {
        $pub_id = chado_get_id_from_nid('pub', $node->nid);
        $path = tripal_pub_set_pub_url($node, $pub_id);
      }
    }
  }
}

/**
 * Implements hook_node_update(). Acts on all content types.
 *
 * We want the publications to always have a URL of http://[base url]/pub/[pub
 * id] where [pub id] is the Chado publication ID.  This will allow for easy
 * linking into the publication without needing to know the node.  Of course if
 * you know the node that will still work too (e.g. http://[base
 * url]/node/[node id] so the nodeapi function ensures that the URL path is set
 * after insert or update of the node and when the node is loaded if it hasn't
 * yet been set.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_update($node) {

  if ($node->type == 'chado_pub') {

    // get the pub
    $pub_id = chado_get_id_from_nid('pub', $node->nid);
    $values = ['pub_id' => $pub_id];
    $pub = chado_generate_var('pub', $values);

    // expand the 'text' fields as those aren't included by default
    // and they really shouldn't be so large to cause problems
    $pub = chado_expand_var($pub, 'field', 'pub.title');
    $pub = chado_expand_var($pub, 'field', 'pub.volumetitle');
    $pub = chado_expand_var($pub, 'field', 'pub.uniquename');
    $node->pub = $pub;

    // Now get the title
    $node->title = chado_get_node_title($node);

    tripal_pub_set_pub_url($node, $pub_id);
  }
}

/**
 * Implements hook_node_presave(). Acts on all content types.
 *
 * @ingroup tripal_legacy_pub
 */
function tripal_pub_node_presave($node) {
  switch ($node->type) {
    // This step is for setting the title for the Drupal node.  This title
    // is permanent and thus is created to be unique.  Title changes provided
    // by tokens are generated on the fly dynamically, but the node title
    // seen in the content listing needs to be set here. Do not call
    // the chado_get_node_title() function here to set the title as the node
    // object isn't properly filled out and the function will fail.
    case 'chado_pub':
      // when syncing the details are not present in the $node object
      // as they are when submitted via the form.  Therefore, if we do
      // not see any field values from the form, we assume this fucntion
      // is being called for syncing, so we must set the title accordingly
      if (property_exists($node, 'title')) {
        // do nothing, the title is set
      }
      else {
        if (property_exists($node, 'pub')) {
          // in Drupal a node title can only be 255 characters so we truncate
          // it just in case
          $node->title = substr($node->pub->title, 0, 255);
        }
      }
      break;
  }
}

/**
 * Implements [content_type]_chado_node_default_title_format().
 *
 * Defines a default title format for the Chado Node API to set the titles on
 * Chado pub nodes based on chado fields.
 */
function chado_pub_chado_node_default_title_format() {
  return '[pub.title]';
}

/**
 * Implements [content_type]_chado_node_sync_select_query().
 *
 * Adds a where clause to the query to exclude the NULL pub.
 */
function chado_pub_chado_node_sync_select_query($query) {
  $query['where_clauses']['title'][] = 'pub.title <> :pub_title_null';
  $query['where_args']['title'][':pub_title_null'] = 'NULL';

  return $query;
}
